/** 
 * This file is a part of qloudgen-sec. 
 * You can redistribute qloudgen-sec and/or modify it under the terms of the Lesser GNU General Public License version 3. 
 * qloudgen-sec is distributed WITHOUT ANY WARRANTY. 
 *
 * See the Lesser GNU General Public License for more details at http://www.gnu.org/licenses/. 
 **/
package org.qloudgen.sec.util;

import java.io.InputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>Title: Common Class</p>
 * <p>Description: Common class implementation.</p>
 * <p>Copyright: qloudgen/sec 2012</p>
 * @author <a href="mailto:hyq.dd.sun@gmail.com">glue.3M</a>
 */
public final class ClassUtil {
	/**
	 * Log output.
	 */
	private final static Logger logger = LoggerFactory.getLogger( ClassUtil.class );
	
	/**
	 * Default constructor.
	 */
	private ClassUtil() {
	}
	
	/**
	 * Returns current class intance by name.
	 * <Default constructor>
	 *
	 * @param fqcn
	 * @return T
	 */
	public static <T> T getInstance(String fqcn) {
		return getInstance( fqcn , null );
	}

	/**
	 * Returns current class intance by name and args.
	 * <Custom constructor>
	 *
	 * [Another coding style by class template]
	 * -->	return ( T )( forName( fqcn ).newInstance() );
	 * -->	return ( T )( forName( fqcn ).getConstructor( argTypes ).newInstance( args ) );
	 *
	 * @param fqcn
	 * @param args
	 * @return T
	 */
	public static <T> T getInstance(String fqcn , Object... args) {
		//
		try {
			//
			Class<T> clazz = forName( fqcn );
			//
			if ( args == null ) {
				return clazz.newInstance();
			}
			//
			else {
				//
				Class<?>[] argTypes = new Class[ args.length ];
				for ( int i = 0; i < args.length; i++ ) {
					argTypes[i] = args[i].getClass();
				}
				//
				return clazz.getConstructor( argTypes ).newInstance( args );
			}
		}
		catch (Throwable t) {
			throw new RuntimeException( "Create class instance [" + fqcn + "] error..." , t );
		}
	}

	/**
	 * Returns current class instance by name.
	 *
	 * @param fqcn
	 * @return Class
	 */
	public static <T> Class<T> forName(String fqcn) {
		/**
		 * First check current thread context loader.
		 */
		Class<T> clazz = THREAD_CL_ACCESSOR.loadClass( fqcn );
		if (clazz == null) {
			logger.error( "Unable to load class [{}] from current thread context class loader..." , fqcn );
			clazz = CLASS_CL_ACCESSOR.loadClass( fqcn );
		}
		/**
		 * Second check current class loader.
		 */
		if (clazz == null) {
			logger.error( "Unable to load resource [{}] from current class loader..." , fqcn );
			clazz = SYSTEM_CL_ACCESSOR.loadClass( fqcn );
		}
		/**
		 * Final check current system loader.
		 */
		if (clazz == null) {
			logger.error( "Unable to load resource [{}] from current system class loader..." , fqcn );
			throw new RuntimeException( "Unable to load class [" + fqcn + "] from all current ClassLoader..." );
		}
		//
		return clazz;
	}

	/**
	 * Returns current resource stream by name.
	 *
	 * @param name
	 * @return Class
	 */
	public static InputStream getResourceAsStream(String name) {
		/**
		 * First find resource by current thread context loader.
		 */
		InputStream in = THREAD_CL_ACCESSOR.getResourceStream( name );
		if ( in == null ) {
			logger.error( "Unable to load resource [{}] from current thread context class loader..." , name );
			in = CLASS_CL_ACCESSOR.getResourceStream( name );
		}
		/**
		 * Second find resource by current class loader.
		 */
		if ( in == null ) {
			logger.error( "Unable to load resource [{}] from current class loader..." , name );
			in = SYSTEM_CL_ACCESSOR.getResourceStream( name );
		}
		/**
		 * Final find resource by current system loader.
		 */
		if ( in == null ) {
			logger.error( "Unable to load resource [{}] from current system class loader..." , name );
		}
		//
		return in;
	}

	/**
	 * Current thread context class loader.
	 */
	private static final ClassLoaderAccessor THREAD_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
		/**
		 *
		 *
		 * @return ClassLoader
		 */
		@Override
		protected ClassLoader doGetClassLoader() {
			return Thread.currentThread().getContextClassLoader();
		}
	};

	/**
	 * Current class loader.
	 */
	private static final ClassLoaderAccessor CLASS_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
		/**
		 *
		 *
		 * @return ClassLoader
		 */
		@Override
		protected ClassLoader doGetClassLoader() {
			return ClassUtil.class.getClassLoader();
		}
	};
	
	/**
	 * Current system class loader.
	 */
	private static final ClassLoaderAccessor SYSTEM_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
		/**
		 *
		 *
		 * @return ClassLoader
		 */
		@Override
		protected ClassLoader doGetClassLoader() {
			return ClassLoader.getSystemClassLoader();
		}
	};
	
	/**
	 *
	 */
	private static abstract class ExceptionIgnoringAccessor implements ClassLoaderAccessor {

		/**
		 *
		 *
		 * @param fqcn
		 * @return Class
		 */
		public <T> Class<T> loadClass(String fqcn) {
			Class<T> clazz = null;
			ClassLoader loader = getClassLoader();
			//
			if ( loader != null ) {
				try {
					clazz = ( Class<T> )( loader.loadClass( fqcn ) );
				}
				catch (Throwable t) {
					logger.error( "Unable to load class [{}] from class loader [{}]..." , fqcn , loader );
					logger.trace( "\n{}" , ExceptionUtil.get( t ) );
				}
			}
			//
			return clazz;
		}
		
		/**
		 *
		 *
		 * @param name
		 * @return InputStream
		 */
		public InputStream getResourceStream(String name) {
			InputStream in = null;
			ClassLoader loader = getClassLoader();
			//
			if ( loader != null ) {
				in = loader.getResourceAsStream( name );
			}
			//
			return in;
		}
		
		/**
		 *
		 *
		 * @return ClassLoader
		 */
		protected final ClassLoader getClassLoader() {
			//
			try {
			    return doGetClassLoader();
			}
			catch (Throwable t) {
				logger.error( "Find class loader error...\n{}" , ExceptionUtil.get( t ) );
			}
			//
			return null;
		}
		
		/**
		 *
		 *
		 * @return ClassLoader
		 */
		protected abstract ClassLoader doGetClassLoader();
	}
	
	/**
	 *
	 */
	private static interface ClassLoaderAccessor {
		/**
		 *
		 * @param fqcn
		 * @return Class
		 */
		public <T> Class<T> loadClass(String fqcn);
		
		/**
		 *
		 * @param name
		 * @return InputStream
		 */
		public InputStream getResourceStream(String name);
	}
}